-----------------------------------------------------------------------------
--
-- Module      :  Data.Units.Class
-- Copyright   :
-- License     :  AllRightsReserved
--
-- Maintainer  :  eduard.sergeev@gmail.com
-- Stability   :
-- Portability :
--
-- QNum and QFractional ('quantitified' Num and Fractional from Prelude) classes definitions
--
-----------------------------------------------------------------------------

{-# LANGUAGE RebindableSyntax, MultiParamTypeClasses  #-}

module Data.Units.Class (

   QNum(..),
   QFractional(..),
   QReal(..),
   QIntegral(..),
   QFloating(..),

   even,odd,
   gcd,

   QApplicative(..),

) where


import qualified Prelude as P
import Data.Eq
import Data.Bool
import Data.Function

import Data.TypeLevel.Class
import Data.TypeLevel.Boolean
import Data.Units.Types


-- * QNum typeclass - quantitified "Num"

class QNum q n where
    (+) :: q n u -> q n u -> q n u
    (-) :: q n u -> q n u -> q n u
    (*) :: q n ul -> q n ur -> q n (Multiply ul ur)
    negate :: q n u -> q n u
    abs :: q n u -> q n u
    signum :: q n u -> q n u
    fromInteger :: P.Integer -> q n u


subtract :: (QNum q n) => q n u -> q n u -> q n u 
subtract = flip (-)

infixl 7  *
infixl 6  +,-



-- * QFractional - quantitified "Fractional"

class (QNum q n) => QFractional q n where
    (/) :: q n ul -> q n ur -> q n (Divide ul ur)
    recip               :: q n u -> q n (Divide U u)
    fromRational        :: P.Rational -> q n u

{-
    {-# INLINE recip #-}
    {-# INLINE (/) #-}
    recip x             =  1 / x
    x / y               = x * (recip y)
-}
infixl 7  /


-- * QReal - quantitified "Real"
class (QNum q n) => QReal q n where
    toRational :: q n u -> q P.Rational u 

--type family ToRational n :: *


-- * Quantitified "Integral"
class  (QReal q n) => QIntegral q n where
    quot :: q n ul -> q n ur -> q n (Divide ul ur)
    rem :: q n ul -> q n ur -> q n ul
    div                 :: q n ul -> q n ur -> q n (Divide ul ur)
    mod                 :: q n ul -> q n ur -> q n ul
    quotRem             :: q n ul -> q n ur -> (q n (Divide ul ur) , q n ul)
    divMod              :: q n ul -> q n ur -> (q n (Divide ul ur) , q n ul)
    toInteger           :: q n u -> q P.Integer u


gcd             :: (Eq (q n u), QIntegral q n) => q n u -> q n u -> q n u
gcd 0 0         =  P.error "Prelude.gcd: gcd 0 0 is undefined"
gcd x y         =  gcd' (abs x) (abs y)
                   where gcd' a 0  =  a
                         gcd' a b  =  gcd' b (a `rem` b)


-- * Quantitified Floating
class (QFractional q n) => QFloating q n where
    pi                  :: q n U
    exp, log            :: q n U -> q n U      
    sqrt                :: (EvenPowers u) => q n u -> q n (SquareRoot u)
    (**), logBase       :: q n U -> q n U -> q n U
    sin, cos, tan       :: q n U -> q n U
    asin, acos, atan    :: q n U -> q n U
    sinh, cosh, tanh    :: q n U -> q n U
    asinh, acosh, atanh :: q n U -> q n U


-- * Quantitified "Applicative" instance
class QApplicative q where
    qpure :: b -> q b u
    (<***>) :: q (c -> d) b -> q c e -> q d g



-- * Numeric functions

even, odd       :: (Eq (q n u), QIntegral q n) => q n u -> Bool
even n          =  n `rem` 2 == 0
odd             =  not . even

{-
(^) :: (QNum a, QIntegral b) => a -> b -> a
x0 ^ y0 | y0 < 0    = error "Negative exponent"
        | y0 == 0   = 1
        | otherwise = f x0 y0
    where -- f : x0 ^ y0 = x ^ y
          f x y | even y    = f (x * x) (y `quot` 2)
                | y == 1    = x
                | otherwise = g (x * x) ((y - 1) `quot` 2) x
          -- g : x0 ^ y0 = (x ^ y) * z
          g x y z | even y = g (x * x) (y `quot` 2) z
                  | y == 1 = x * z
                  | otherwise = g (x * x) ((y - 1) `quot` 2) (x * z)
-}